package org.anderground.app.storage;

import java.io.IOException;

import org.anderground.app.storage.ConfigData.ConfigCity;
import org.anderground.core.model.ModelData;
import org.anderground.core.storage.StorageException;

/**
 * @author Eldar Abusalimov
 */
public abstract class AppStorage {

	private ConfigData configData;

	/**
	 * TODO in debug purposes.
	 */
	public void dumpConfig() {
		configData.dump();
	}

	/**
	 * 
	 * @param cityId
	 * @return
	 * @throws NotYetConfiguredException
	 */
	public boolean cityExists(long cityId) throws NotYetConfiguredException {
		if (configData == null) {
			throw new NotYetConfiguredException();
		}
		return configData.getCity((int) cityId) != null;
	}

	/**
	 * 
	 * @param cityId
	 * @return
	 * @throws IOException
	 * @throws NotYetConfiguredException
	 * @throws NoSuchCityException
	 */
	public final ModelData read(long cityId) throws IOException,
			NotYetConfiguredException, NoSuchCityException {
		return readFromStorage(this, idToCity(cityId));
	}

	/**
	 * 
	 * @param data
	 * @param cityId
	 * @throws IOException
	 * @throws NotYetConfiguredException
	 * @throws NoSuchCityException
	 */
	public final void write(ModelData data, long cityId) throws IOException,
			NotYetConfiguredException, NoSuchCityException {
		writeToStorage(this, data, idToCity(cityId));
	}

	/**
	 * Allows to read a data from any {@link AppStorage} either configured or
	 * not.
	 * 
	 * @param storage
	 * @param city
	 * @return
	 * @throws IOException
	 */
	protected static final ModelData readFromStorage(AppStorage storage,
			ConfigCity city) throws IOException {
		ModelData data = storage.readModelImpl(city);
		if (data == null) {
			throw new StorageException("null ModelData was returned");
		}
		return data;
	}

	/**
	 * Allows to write a data to any {@link AppStorage} either configured or
	 * not.
	 * 
	 * @param storage
	 * @param data
	 * @param city
	 * @throws IOException
	 */
	protected static final void writeToStorage(AppStorage storage,
			ModelData data, ConfigCity city) throws IOException {
		if (data == null) {
			throw new StorageException("cannot write null ModelData");
		}
		storage.writeModelImpl(data, city);
	}

	/**
	 * Convenient method for getting the city mapped from its id.
	 * 
	 * @param cityId
	 * @return not null {@link ConfigCity} mapped from the specified cityId
	 * @throws NotYetConfiguredException
	 * @throws NoSuchCityException
	 */
	private ConfigCity idToCity(long cityId) throws NotYetConfiguredException,
			NoSuchCityException {
		if (configData == null) {
			throw new NotYetConfiguredException();
		}
		ConfigCity city = configData.getCity((int) cityId);
		if (city == null) {
			throw new NoSuchCityException(cityId);
		}
		return city;
	}

	/**
	 * 
	 * @param storage
	 * @param onProgressCallback
	 * @return
	 * @throws IOException
	 */
	protected static final ConfigData readConfigFromStorage(AppStorage storage,
			OnProgressCallback onProgressCallback) throws IOException {
		ConfigData data = storage.readConfigImpl(onProgressCallback);
		if (data == null) {
			throw new StorageException("null ConfigData was returned");
		}
		storage.configData = data;
		return data;
	}

	/**
	 * 
	 * @param storage
	 * @param data
	 * @param onProgressCallback
	 * @throws IOException
	 */
	protected static final void writeConfigToStorage(AppStorage storage,
			ConfigData data, OnProgressCallback onProgressCallback)
			throws IOException {
		if (data == null) {
			throw new StorageException("cannot write null ConfigData");
		}
		storage.writeConfigImpl(data, onProgressCallback);
		storage.configData = data;
	}

	/**
	 * @return the configData
	 */
	protected final ConfigData getConfigData() {
		return configData;
	}

	/**
	 * Implementation should read a {@link ModelData} from the storage of the
	 * specified {@link ConfigCity}.
	 * 
	 * @param city
	 *            the city definition used for reading the ModelData from the
	 *            appropriate place
	 * @return not null ModelData instance (a {@link IOException} should be
	 *         thrown instead of returning null)
	 * @throws IOException
	 *             if any errors have occurred and data cannot be read
	 */
	protected abstract ModelData readModelImpl(ConfigCity city)
			throws IOException;

	/**
	 * Implementation should write the {@link ModelData} to the storage of the
	 * specified {@link ConfigCity}.
	 * 
	 * @param data
	 *            the data to write (if anything goes wrong a
	 *            {@link IOException} should be thrown instead of silently
	 *            ignoring writing failure)
	 * @param city
	 *            the city definition used for writing the ModelData to the
	 *            appropriate place
	 * @throws IOException
	 *             if any errors have occurred and the data cannot be written
	 */
	protected abstract void writeModelImpl(ModelData data, ConfigCity city)
			throws IOException;

	/**
	 * 
	 * @param onProgressCallback
	 * @return
	 * @throws IOException
	 */
	protected abstract ConfigData readConfigImpl(
			OnProgressCallback onProgressCallback) throws IOException;

	/**
	 * 
	 * @param data
	 * @param onProgressCallback
	 * @throws IOException
	 */
	protected abstract void writeConfigImpl(ConfigData data,
			OnProgressCallback onProgressCallback) throws IOException;

}
